/******************************************************************************
 * This file is part of project ORCA. More information on the project
 * can be found at the following repositories at GitHub's website.
 *
 * http://https://github.com/andersondomingues/orca-sim
 * http://https://github.com/andersondomingues/orca-software
 * http://https://github.com/andersondomingues/orca-mpsoc
 * http://https://github.com/andersondomingues/orca-tools
 *
 * Copyright (C) 2018-2020 Anderson Domingues, <ti.andersondomingues@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
******************************************************************************/
#include <cstdlib>
#include <sstream>
#include <chrono>

#include "sys/time.h"

#include "HFRiscV.hpp"
#include "Signal.hpp"

#define RISCV_INVALID_OPCODE 0x0

// api access shorthands (must be undef at the end of the file)
#define PC GetState()->pc
#define PC_NEXT GetState()->pc_next
#define R GetState()->regs

using orcasim::models::hfriscv::HFRiscV;

void HFRiscV::dumpregs() {
    for (uint32_t i = 0; i < 32; i += 4) {
        printf("r%02d [%08x] r%02d [%08x] r%02d [%08x] r%02d [%08x]\n", \
        i, R[i], i+1, R[i+1], i+2, R[i+2], i+3, R[i+3]);
    }

    printf("pc: %08x\n\n", PC);
}

void HFRiscV::bp(risc_v_state *s, uint32_t ir) {
    printf("Breakpoint reached at 0x%x (ir: %08x)", PC, ir);
    printf("irq_status: %08x, irq_cause: %08x, irq_mask: %08x\n", s->status,
        s->cause, s->mask);
    dumpregs();

    #ifdef ORCA_ENABLE_GDBRSP
    GetState()->bp = 0x1;
    #endif
}

/**
 * @brief Reads data from the memory
 * @param s Current state of the core
 * @param size Size of data to be read. Must be 32, 16, or 8.
 * @param address Starting address to read from
 * @return Data read
 */
int32_t HFRiscV::mem_read(risc_v_state *s, int32_t size, uint32_t address) {
    uint32_t data;

    // Check whether the address belongs to the main memory
    if (address <= GetMemory()->GetLastAddr() && address >
        GetMemory()->GetBase()) {
        #ifdef HFRISCV_ENABLE_COUNTERS
        // get information on host's clock (real clock, in hours, not cycles)
        if (address == _counter_hosttime->GetAddress()) {
            std::chrono::time_point<std::chrono::system_clock> now
                = std::chrono::system_clock::now();
            auto duration = now.time_since_epoch();
            auto millis =
                std::chrono::duration_cast<std::chrono::milliseconds>(duration)
                    .count();

            _counter_hosttime->Write(millis);
        }
        #endif

        switch (size) {
        case 4:
            if (address & 3) {
                std::string err_msg = GetName() +
                    ": unaligned access (load word) pc=0x" +
                    std::to_string(PC) + " addr=0x" + std::to_string(address);
                throw std::runtime_error(err_msg);
            } else {
                // 4 x sizeof(uint8_t)
                GetMemory()->Read(address, reinterpret_cast<int8_t*>(&data), 4);
            }
            break;
        case 2:
            if (address & 1) {
                std::string err_msg = GetName()
                    + ": unaligned access (load halfword) pc=0x"
                    + std::to_string(PC) + " addr=0x" + std::to_string(address);
                throw std::runtime_error(err_msg);
            } else {
                int16_t value;
                // 2 x sizeof(uint8_t)
                GetMemory()->Read(address,
                    reinterpret_cast<int8_t*>(&value), 2);
                data = value;
            }
            break;
        case 1:
            int8_t value;
            GetMemory()->Read(address, &value, 1);  // 1 x sizeof(uint8_t)
            data = value;
            break;
        default:
            std::string err_msg = GetName() +
                ": could not read from memory, invalid data size requested";
            throw std::runtime_error(err_msg);
        }

        return data;
    } else {
        // Address does not belong to any bank, check for special addresses
        switch (address) {
            case IRQ_VECTOR:    return s->vector;
            case IRQ_CAUSE:     return s->cause | 0x0080 | 0x0040;
            case IRQ_MASK:      return s->mask;
            case IRQ_STATUS:    return s->status;
            case IRQ_EPC:       return s->epc;
            case COUNTER:       return s->counter;
            case COMPARE:       return s->compare;
            case COMPARE2:      return s->compare2;
            case UART_READ:     return getchar();
            case UART_DIVISOR:  return 0;
        }

        // may the requested address fall in unmapped range, halt the simulation
        dumpregs();
        std::stringstream ss;
        ss << GetName() << ": unable to read from unmapped memory space 0x"
            << std::hex << address << ".";
        throw std::runtime_error(ss.str());
    }
}

Signal<uint8_t>* HFRiscV::GetSignalStall() {
    return _signal_stall;
}

Signal<uint8_t>* HFRiscV::GetSignalIntr() {
    return _signal_intr;
}

/**
 * @brief Reads data from memory
 * @param s The current state of the processor
 * @param size Size of data to be read. Must be 32, 16 or 8.
 * @param address Starting address of data
 * @param value Value to be written to the address
 */
void HFRiscV::mem_write(risc_v_state *s, int32_t size, uint32_t address,
    uint32_t value) {
    // if the address belong to some memory range, write to it
    if (address <= GetMemory()->GetLastAddr()) {
        switch (size) {
            case 4:
                if (address & 3) {
                    std::stringstream ss;
                    ss << GetName() << ": unaligned access (store word) pc=0x"
                        << std::hex << PC << " addr=0x" << std::hex << address;
                    throw std::runtime_error(ss.str());
                } else {
                    GetMemory()->Write(address,
                        reinterpret_cast<int8_t*>(&value), size);
                }
                break;
            case 2:
                if (address & 1) {
                    std::string err_msg = GetName()
                        + ": unaligned access (store halfword) pc=0x"
                        + std::to_string(PC) + " addr=0x"
                        + std::to_string(address);
                    throw std::runtime_error(err_msg);
                } else {
                    uint16_t data = static_cast<uint16_t>(value);
                    GetMemory()->Write(address,
                        reinterpret_cast<int8_t*>(&data), size);
                }
                break;
            case 1: {
                uint8_t data;
                data = (uint8_t)value;
                GetMemory()->Write(address,
                    reinterpret_cast<int8_t*>(&data), size);
                break;
            }
            default: {
                dumpregs();
                std::stringstream ss;
                ss << GetName()
                    << ": unable to write to unmapped memory space 0x"
                    << std::hex << address << ".";
                throw std::runtime_error(ss.str());
            }
        }
        return;  // succefully written
    }

    // may the request memory space be out of the mapped memory range, we assume
    // the code is pointing to some of the special addresses
    switch (address) {
        case IRQ_STATUS: {
            if (value == 0) {
                s->status = 0;
                for (int i = 0; i < 4; i++)
                    s->status_dly[i] = 0;
            } else {
                s->status_dly[3] = value;
            }
            return;
        }

        case IRQ_VECTOR:
            s->vector = value;
            return;
        case IRQ_CAUSE:
            s->cause = value;
            return;
        case IRQ_MASK:
            s->mask = value;
            return;
        case IRQ_EPC:
            s->epc = value;
            return;
        case COUNTER:
            s->counter = value;
            return;
        case COMPARE:
            s->compare = value;
            s->cause &= 0xffef;
            return;
        case COMPARE2:
            s->compare2 = value;
            s->cause &= 0xffdf;
            return;

        case DEBUG_ADDR:
            output_debug << (int8_t)(value & 0xff) << std::flush;
            return;
        case UART_WRITE:
            output_uart << (int8_t)(value & 0xff) << std::flush;
            return;
        case UART_DIVISOR: return;

        case EXIT_TRAP:

            // write cause of aborting to special register
            GetState()->terminated = value;
            printf("%s: exit trap triggered with status 0x%x\n",
                GetName().c_str(), value);

            output_debug.close();
            output_uart.close();

            return;
    }

    // if none of the special address has been reach, the requested
    // address if unknown to the system and we should halt the simulation
    dumpregs();
    std::stringstream ss;

    ss << GetName() << ": unable to write to unmapped memory space 0x"
        << std::hex << address << ".";

    throw std::runtime_error(ss.str());
}

#ifdef HFRISCV_ENABLE_COUNTERS

// Counters' getters
Signal<uint32_t>* HFRiscV::GetSignalCounterArith() {
    return _counter_iarith;
}
Signal<uint32_t>* HFRiscV::GetSignalCounterLogical() {
    return _counter_ilogical;
}
Signal<uint32_t>* HFRiscV::GetSignalCounterShift() {
    return _counter_ishift;
}
Signal<uint32_t>* HFRiscV::GetSignalCounterBranches() {
    return _counter_ibranches;
}
Signal<uint32_t>* HFRiscV::GetSignalCounterJumps() {
    return _counter_ijumps;
}
Signal<uint32_t>* HFRiscV::GetSignalCounterLoadStore() {
    return _counter_iloadstore;
}

// cycles
Signal<uint32_t>* HFRiscV::GetSignalCounterCyclesTotal() {
    return _counter_cycles_total;
}
Signal<uint32_t>* HFRiscV::GetSignalCounterCyclesStall() {
    return _counter_cycles_stall;
}
Signal<uint32_t>* HFRiscV::GetSignalHostTime() {
    return _counter_hosttime;
}

/**
 * Update Counters
 * Counters are incremented by one every time some instruction
 * is executed. Please note that NOP and MOVE instructions are
 * ignored as riscv32i does not generate them.
*/
void HFRiscV::UpdateCounters(int opcode, int funct3) {
    switch (opcode) {
        case 0x37:  // LUI
        case 0x17:  // ALUI
        case 0x6f:  // JAL
        case 0x67:  // JALR
            _counter_ijumps->Inc(1);
            break;

        case 0x63:  // branches
            _counter_ibranches->Inc(1);
            break;

        case 0x3:  // loads
        case 0x23:  // stores
            _counter_iloadstore->Inc(1);
            break;

        case 0x13:  // type R
        case 0x33:
            switch (funct3) {
                case 0x0:  // addi, subi
                    _counter_iarith->Inc(1);
                    break;
                case 0x4:  // xori
                case 0x6:  // ori
                case 0x7:  // andi
                    _counter_ilogical->Inc(1);
                    break;

                default:  // shifts i
                    _counter_ishift->Inc(1);
                    break;
            }
            break;
        default:
            std::cout << "wrn: opcode not accounted for energy estimation"
                << std::endl;
            break;
    }
}
#endif /* HFRISCV_ENABLE_COUNTERS */

SimulationTime HFRiscV::Run() {
    // call ancestor method which handles
    // generic tasks all processor models
    ProcessorBase::Run();

    #ifdef ORCA_ENABLE_GDBRSP
    // When operating with GDB support, the pause flags indicates
    // the processor core must skip the current cycle without mo-
    // fiying any of their registers. This flags acts as a second
    // stall line. Since this line is used only by the gbdrsp imp-
    // lementation, we do not set this flag as a wire, as it does
    // not exist in the real design (yet).
    if (GetState()->pause == 0x1)
        return 1;
    #endif

    // update "external counters"
    s->counter++;

    /*IRQ_COMPARE2*/
    if ((s->compare2 & 0xffffff) == (s->counter & 0xffffff)) s->cause |= 0x20;
    /*IRQ_COMPARE*/
    if (s->compare == s->counter) s->cause |= 0x10;
    /*IRQ_COUNTER2_NOT*/
    if (!(s->counter & 0x10000))
        s->cause |= 0x8;
    else
        s->cause &= 0xfffffff7;
    /*IRQ_COUNTER2*/
    if (s->counter & 0x10000)
        s->cause |= 0x4;
    else
        s->cause &= 0xfffffffb;
    /*IRQ_COUNTER_NOT*/
    if (!(s->counter & 0x40000))
        s->cause |= 0x2;
    else
        s->cause &= 0xfffffffd;
    /*IRQ_COUNTER*/
    if (s->counter & 0x40000)
        s->cause |= 0x1;
    else
        s->cause &= 0xfffffffe;
    /*IRQ_NOC*/
    if (_signal_intr->Read() == 0x1)
        s->cause |= 0x100;
    else
        s->cause &= 0xfffffeff;

    // skip current cycle if stall is risen
    if (_signal_stall->Read() == 0x1) {
        #ifdef HFRISCV_ENABLE_COUNTERS
        _counter_cycles_stall->Inc(1);
        #endif
        return 1;
    }

    #ifdef HFRISCV_ENABLE_COUNTERS
    _counter_cycles_total->Inc(1);
    #endif

    #ifdef HFRISCV_CYCLE_ACCURACY
    uint32_t pc_next_prediction;
    #endif

    uint32_t inst, i;
    uint32_t opcode, rd, rs1, rs2, funct3, funct7,
        imm_i, imm_s, imm_sb, imm_u, imm_uj;
    int32_t *r = s->r;
    uint32_t *u = reinterpret_cast<uint32_t *>(s->r);
    uint32_t ptr_l, ptr_s;

    #ifdef HFRISCV_CYCLE_ACCURACY
    uint32_t branch_taken = 0;
    #endif

    if (s->status && (s->cause & s->mask)) {
        s->epc = PC_NEXT;
        PC = s->vector;
        PC_NEXT = s->vector + 4;
        s->status = 0;
        for (i = 0; i < 4; i++)
            s->status_dly[i] = 0;
    }

    // FETCH STAGE
    // 4 x sizeof(uint8_t)
    GetMemory()->Read(PC, reinterpret_cast<int8_t*>(&inst), 4);

    // DECODE
    opcode = inst & 0x7f;

    rd = (inst >> 7) & 0x1f;
    rs1 = (inst >> 15) & 0x1f;
    rs2 = (inst >> 20) & 0x1f;
    funct3 = (inst >> 12) & 0x7;
    funct7 = (inst >> 25) & 0x7f;
    imm_i = (inst & 0xfff00000) >> 20;
    imm_s = ((inst & 0xf80) >> 7) | ((inst & 0xfe000000) >> 20);
    imm_sb = ((inst & 0xf00) >> 7) | ((inst & 0x7e000000) >> 20)
        | ((inst & 0x80) << 4) | ((inst & 0x80000000) >> 19);
    imm_u = inst & 0xfffff000;
    imm_uj = ((inst & 0x7fe00000) >> 20) | ((inst & 0x100000) >> 9)
        | (inst & 0xff000) | ((inst & 0x80000000) >> 11);

    if (inst & 0x80000000) {
        imm_i |= 0xfffff000;
        imm_s |= 0xfffff000;
        imm_sb |= 0xffffe000;
        imm_uj |= 0xffe00000;
    }
    ptr_l = r[rs1] + (int32_t)imm_i;
    ptr_s = r[rs1] + (int32_t)imm_s;
    r[0] = 0;

    // EXECUTE
    switch (opcode) {
        case 0x37: r[rd] = imm_u; break;  // LUI
        case 0x17: r[rd] = PC + imm_u; break;  // AUIPC
        case 0x6f: r[rd] = PC_NEXT; PC_NEXT = PC + imm_uj; break;  // JAL
        case 0x67:  // JALR
            r[rd] = PC_NEXT;
            PC_NEXT = (r[rs1] + imm_i) & 0xfffffffe;
            break;
        case 0x63:
            /* Branch prediction may fail if jumping 0 positions.
            TODO: check whether the architecture predict such jumps */
            #ifdef HFRISCV_CYCLE_ACCURACY
            pc_next_prediction = PC_NEXT;
            #endif

            switch (funct3) {
                case 0x0:  // BEQ
                    if (r[rs1] == r[rs2]) { PC_NEXT = PC + imm_sb; }
                    break;
                case 0x1:  // BNE
                    if (r[rs1] != r[rs2]) { PC_NEXT = PC + imm_sb; }
                    break;
                case 0x4:  // BLT
                    if (r[rs1] < r[rs2]) { PC_NEXT = PC + imm_sb; }
                    break;
                case 0x5:  // BGE
                    if (r[rs1] >= r[rs2]) { PC_NEXT = PC + imm_sb; }
                     break;
                case 0x6:  // BLTU
                    if (u[rs1] < u[rs2]) { PC_NEXT = PC + imm_sb; }
                     break;
                case 0x7:  // BGEU
                    if (u[rs1] >= u[rs2]) { PC_NEXT = PC + imm_sb; }
                     break;
                default:
                    goto fail;
            }

            #ifdef HFRISCV_CYCLE_ACCURACY
            branch_taken = (pc_next_prediction != PC_NEXT);
            #endif

            break;

        case 0x3:
            switch (funct3) {
                case 0x0:  // LB
                    r[rd] = static_cast<int8_t>(mem_read(s, 1, ptr_l));
                    break;
                case 0x1:  // LH
                    r[rd] = static_cast<int16_t>(mem_read(s, 2, ptr_l));
                    break;
                case 0x2:  // LW
                    r[rd] = mem_read(s, 4, ptr_l);
                    break;
                case 0x4:  // LBU
                    r[rd] = static_cast<uint8_t>(mem_read(s, 1, ptr_l));
                    break;
                case 0x5:  // LHU
                    r[rd] = static_cast<uint16_t>(mem_read(s, 2, ptr_l));
                    break;
                default: goto fail;
            }
            break;
        case 0x23:
            switch (funct3) {
                case 0x0:  // SB
                    mem_write(s, 1, ptr_s, r[rs2]);
                    break;
                case 0x1:  // SH
                    mem_write(s, 2, ptr_s, r[rs2]);
                    break;
                case 0x2:  // SW
                    mem_write(s, 4, ptr_s, r[rs2]);
                    break;
                default: goto fail;
            }
            break;
        case 0x13:
            switch (funct3) {
                case 0x0:  // ADDI
                    r[rd] = r[rs1] + static_cast<int32_t>(imm_i);
                    break;
                case 0x2:  // SLTI
                    r[rd] = r[rs1] < static_cast<int32_t>(imm_i);
                    break;
                case 0x3:  // SLTIU
                    r[rd] = u[rs1] < static_cast<uint32_t>(imm_i);
                    break;
                case 0x4:  // XORI
                    r[rd] = r[rs1] ^ static_cast<int32_t>(imm_i);
                    break;
                case 0x6:  // ORI
                    r[rd] = r[rs1] | static_cast<int32_t>(imm_i);
                    break;
                case 0x7:  // ANDI
                    r[rd] = r[rs1] & static_cast<int32_t>(imm_i);
                    break;
                case 0x1:  // SLLI
                    r[rd] = u[rs1] << (rs2 & 0x3f);
                    break;
                case 0x5:
                    switch (funct7) {
                        case 0x0:  // SRLI
                            r[rd] = u[rs1] >> (rs2 & 0x3f);
                            break;
                        case 0x20:  // SRAI
                            r[rd] = r[rs1] >> (rs2 & 0x3f);
                            break;
                        default: goto fail;
                    }
                    break;
                default: goto fail;
            }
            break;
        case 0x33:
            if (funct7 == 0x1) {  // RV32M
                switch (funct3) {
                    case 0:  // MUL
                        r[rd] = (((int64_t)r[rs1] * (int64_t)r[rs2])
                            & 0xffffffff);
                        break;
                    case 1:  // MULH
                        r[rd] = ((((int64_t)r[rs1] * (int64_t)r[rs2]) >> 32)
                            & 0xffffffff);
                        break;
                    case 2:  // MULHSU
                        r[rd] = ((((int64_t)r[rs1] * (uint64_t)u[rs2]) >> 32)
                            & 0xffffffff);
                        break;
                    case 3:  // ULHU
                        r[rd] = ((((uint64_t)u[rs1] * (uint64_t)u[rs2]) >> 32)
                            & 0xffffffff);
                        break;
                    case 4:  // DIV
                        if (r[rs2])
                            r[rd] = r[rs1] / r[rs2];
                        else
                            r[rd] = 0;
                        break;
                    case 5:  // DIVU
                        if (r[rs2])
                            r[rd] = u[rs1] / u[rs2];
                        else
                            r[rd] = 0;
                        break;
                    case 6:  // REM
                        if (r[rs2])
                            r[rd] = r[rs1] % r[rs2];
                        else
                            r[rd] = 0;
                        break;
                    case 7:  // REMU
                        if (r[rs2])
                            r[rd] = u[rs1] % u[rs2];
                        else
                            r[rd] = 0;
                        break;
                    default: goto fail;
                }
                break;
            } else {
                switch (funct3) {
                    case 0x0:
                        switch (funct7) {
                            case 0x0:  // ADD
                                r[rd] = r[rs1] + r[rs2];
                                break;
                            case 0x20:  // SUB
                                r[rd] = r[rs1] - r[rs2];
                                break;
                            default: goto fail;
                        }
                        break;
                    case 0x1:  // SLL
                        r[rd] = r[rs1] << r[rs2];
                        break;
                    case 0x2:  // SLT
                        r[rd] = r[rs1] < r[rs2];
                        break;
                    case 0x3:  // SLTU
                        r[rd] = u[rs1] < u[rs2];
                        break;
                    case 0x4:  // XOR
                        r[rd] = r[rs1] ^ r[rs2];
                        break;
                    case 0x5:
                        switch (funct7) {
                            case 0x0:  // SRL
                                r[rd] = u[rs1] >> u[rs2];
                                break;
                            case 0x20:  // SRA
                                r[rd] = r[rs1] >> r[rs2];
                                break;
                            default: goto fail;
                        }
                        break;
                    case 0x6: r[rd] = r[rs1] | r[rs2]; break;  // OR
                    case 0x7: r[rd] = r[rs1] & r[rs2]; break;  // AND
                    default: goto fail;
                }
                break;
            }
            break;
        default:
fail:
            std::stringstream ss;
            ss << GetName() << ":invalid opcode (at pc=0x" << std::hex << PC;
            ss << " opcode=0x" << std::hex << inst << ")";

            dumpregs();
            bp(s, RISCV_INVALID_OPCODE);

            throw std::runtime_error(ss.str());
            break;
    }

    _last_pc = PC;
    PC = PC_NEXT;
    PC_NEXT = PC_NEXT + 4;
    s->status = s->status_dly[0];

    for (i = 0; i < 3; i++)
        s->status_dly[i] = s->status_dly[i+1];

    #ifdef HFRISCV_ENABLE_COUNTERS
    UpdateCounters(opcode, funct3);
    #endif

    #ifdef HFRISCV_CYCLE_ACCURACY
    // When in cycle-accuracy mode, takes three cycles per instruction,
    // except for those of memory I/O. In the later case. Since we simulate
    // the pipeline by executing one instruction per cycle (starting from
    // the 3th cycle), adding 1 cycle to simulate I/O delay. We also calculate
    // branch prediction.
    switch (opcode) {
        case 0x63:
            return (branch_taken) ? 1 : 2;
            break;
        case 0x23:
        case 0x3:
            return 2;
            break;
        default:
            return 1;
            break;
    }
    #else
    // When in instruction mode, evey instruction takes exactly one cycle to
    // leave the pipeline. This mode aims for performance.
    return 1;
    #endif
}

HFRiscV::HFRiscV(std::string name, Signal<uint8_t>* intr,
    Signal<uint8_t>* stall, Memory* mainmem)
    : ProcessorBase(name, HFRISCV_PC_MEMBASE, mainmem) {

    s = new risc_v_state;
    memset(s, 0, sizeof(risc_v_state));

    PC = HFRISCV_PC_MEMBASE;
    PC_NEXT = PC + 4;

    s->vector = 0;
    s->cause = 0;
    s->mask = 0;
    s->status = 0;

    for (i = 0; i < 4; i++)
        s->status_dly[i] = 0;

    s->epc = 0;
    s->counter = 0;
    s->compare = 0;
    s->compare2 = 0;
    // s->cycles = 0;

    // set interruption wire (to be managed by the top-level module)
    _signal_intr = intr;
    _signal_stall = stall;

    output_debug.open("logs/" + GetName()
        + "_debug.log", std::ofstream::out | std::ofstream::trunc);
    output_uart.open("logs/" + GetName()
        + "_uart.log", std::ofstream::out | std::ofstream::trunc);

    #ifdef HFRISCV_ENABLE_COUNTERS
    _counter_iarith = new Signal<uint32_t>(GetName() + ".counters.iarith");
    _counter_ilogical = new Signal<uint32_t>(GetName() + ".counters.ilogical");
    _counter_ishift = new Signal<uint32_t>(GetName() + ".counters.ishift");
    _counter_ibranches =
        new Signal<uint32_t>(GetName() + ".counters.ibranches");
    _counter_ijumps = new Signal<uint32_t>(GetName() + ".counters.ijumps");
    _counter_iloadstore =
        new Signal<uint32_t>(GetName() + ".counters.iloadstore");
    _counter_cycles_total =
        new Signal<uint32_t>(GetName() + ".counters.cycles_total");
    _counter_cycles_stall =
        new Signal<uint32_t>(GetName() + ".counters.cycles_stall");

    _counter_hosttime = new Signal<uint32_t>(GetName() + ".counters.hosttime");
    #endif
}

HFRiscV::~HFRiscV() {
    #ifdef HFRISCV_ENABLE_COUNTERS
    delete _counter_iarith;
    delete _counter_ilogical;
    delete _counter_ishift;
    delete _counter_ibranches;
    delete _counter_ijumps;
    delete _counter_iloadstore;

    delete _counter_cycles_total;
    delete _counter_cycles_stall;

    delete _counter_hosttime;

    #endif
}

void HFRiscV::Reset() {
    // TODO(ad): to be implemented
    return;
}

// api access shorthands (must be undef at the end of the file)
#undef PC
#undef PC_NEXT
#undef R
